import os.path
import re
import webbrowser
import pandas as pd

import openpyxl
from PySide2 import QtWidgets
from PySide2.QtCore import QDateTime, QDate
from PySide2.QtGui import QColor, Qt
from PySide2.QtWidgets import QTableWidgetItem, QFileDialog, QMenu, QApplication, QMessageBox
from openpyxl.styles import PatternFill, Border, Side

from ui.tab_widget.dichotomy_widget import Ui_DichotomyWidget
from utils.file_utils import Config
from utils.task import QTask


class Dichotomy(QtWidgets.QWidget, Ui_DichotomyWidget):
    color_dict = {
        '未复现': '#00FF00',
        '复现': '#FF0000',
        '异常': '#FF9900',
        '排除': '#999999',
    }

    def __init__(self, parent):
        super(Dichotomy, self).__init__(parent)
        self.setupUi(self)
        self.parent = parent

        self.task = QTask()
        self.project_dict = {}

        self.comboBoxProject.currentTextChanged.connect(self.update_combobox_branch)
        self.checkBoxUboot.clicked.connect(self.remind_user_risk)
        self.pushButtonImport.clicked.connect(self.import_dichotomy_result)
        self.pushButtonExport.clicked.connect(self.export_dichotomy_result)
        self.pushButtonReset.clicked.connect(self.reset_dichotomy_task)

        self.init_version_build_tab()
        self.init_trigger_build_tab()
        self.init_tag_build_tab()

        self.stage = None
        self.loop_times = 0
        self.download_progress = ''

        self.current_version_row = 0
        self.current_trigger_row = 0
        self.current_tag_row = 0

        self.confirmed = True
        self.init_project_branches_combobox()

    def init_project_branches_combobox(self):
        # 初始化项目和分支下拉列表
        self.task.rsp_sgl.connect(self._add_project_branches_items)
        self.task.send('get_ci_projects')

    def _add_project_branches_items(self, projects):
        self.task.rsp_sgl.disconnect(self._add_project_branches_items)
        self.comboBoxProject.clear()
        self.comboBoxBranch.clear()
        self.project_dict.clear()
        for project in projects:
            project_name = project['projectName']
            branches = [b['branch'] for b in project['branchs']]
            self.project_dict.update({project_name: branches})
            self.comboBoxProject.addItem(project_name)
        # 默认为 openharmony 项目
        if 'openharmony' in self.project_dict:
            self.comboBoxProject.setCurrentText('openharmony')

    def update_combobox_branch(self, project):
        branches = self.project_dict.get(project, [])
        self.comboBoxBranch.addItems(branches)
        if 'master' in branches:
            self.comboBoxBranch.setCurrentText('master')

    def remind_user_risk(self):
        if self.checkBoxUboot.checkState() == Qt.Checked:
            ans = QMessageBox.warning(self, self.windowTitle(), '如果你的设备不是8G的设备，刷机时使用8G的uboot.img可能会导致设备变砖并且无法救机\n\n你确认要刷入8G的uboot.img吗？',
                                      QMessageBox.Yes | QMessageBox.Cancel)
            if ans != QMessageBox.Yes:
                self.checkBoxUboot.setCheckState(Qt.Unchecked)
                self.checkBoxUboot.setCheckState(Qt.Unchecked)

    def reset_dichotomy_task(self):
        if self.loop_times:
            answer = QMessageBox.warning(self, self.windowTitle(), '当前二分任务未结束，确定需要重置二分任务吗？', QMessageBox.Yes | QMessageBox.No)
            if answer != QMessageBox.Yes:
                return

        self.init_task()
        while self.tableWidgetVersionBuild.rowCount():
            self.tableWidgetVersionBuild.removeRow(0)
        while self.tableWidgetTriggerBuild.rowCount():
            self.tableWidgetTriggerBuild.removeRow(0)
        while self.tableWidgetTagBuild.rowCount():
            self.tableWidgetTagBuild.removeRow(0)
        self.textBrowser.clear()
        self.textBrowserConsole.clear()

    def switch_tab_status(self, status: bool):
        self.comboBoxProject.setEnabled(status)
        self.comboBoxBranch.setEnabled(status)
        self.checkBoxUboot.setEnabled(status)
        self.pushButtonImport.setEnabled(status)
        self.pushButtonExport.setEnabled(status)
        self.pushButtonReset.setEnabled(status)
        # 版本二分
        self.dateEditVersionStart.setEnabled(status)
        self.dateEditVersionEnd.setEnabled(status)
        self.comboBoxVComponent.setEnabled(status)
        self.toolButtonBuildComponent.setEnabled(status)
        self.pushButtonFetchVerBuild.setEnabled(status)
        # 调度时间二分
        self.comboBoxTrComponent.setEnabled(status)
        self.dateTimeEditTriggerStart.setEnabled(status)
        self.dateTimeEditTriggerEnd.setEnabled(status)
        self.pushButtonFetchTriggerBuild.setEnabled(status)
        # tag点二分
        self.comboBoxTComponent.setEnabled(status)
        self.comboBoxManifest.setEnabled(status)
        self.toolButtonFetchManifest.setEnabled(status)
        self.dateTimeEditTagStart.setEnabled(status)
        self.dateTimeEditTagEnd.setEnabled(status)
        self.lineEditPRKeywords.setEnabled(status)
        self.pushButtonFetchTagBuild.setEnabled(status)

    def init_version_build_tab(self):
        self.dateEditVersionStart.setDate(QDate.currentDate())
        self.dateEditVersionEnd.setDate(QDate.currentDate())
        self.comboBoxVComponent.currentTextChanged.connect(self.sync_trigger_tag_component)
        self.toolButtonBuildComponent.clicked.connect(self.fetch_all_build_streams)
        self.pushButtonFetchVerBuild.clicked.connect(self.fetch_daily_build_version)
        # 表格添加头部点击取消选择事件和表格内容右键菜单
        self.tableWidgetVersionBuild.horizontalHeader().sectionClicked.connect(self.tableWidgetVersionBuild.clearSelection)
        self.tableWidgetVersionBuild.setContextMenuPolicy(Qt.CustomContextMenu)
        self.tableWidgetVersionBuild.customContextMenuRequested.connect(self.show_version_build_table_menu)

    def fetch_all_build_streams(self):
        """
        获取所有符合条件的流水线
        :return:
        """
        project = self.comboBoxProject.currentText()
        branch = self.comboBoxBranch.currentText()
        start_time = self.dateEditVersionStart.date().toString('yyyyMMdd') + '000000'
        end_time = self.dateEditVersionEnd.date().toString('yyyyMMdd') + '235959'
        data = {
            'projectName': project,
            'branch': branch,
            'startTime': start_time,
            'endTime': end_time,
        }
        self.switch_tab_status(False)
        self.task.rsp_sgl.connect(self.add_build_items)
        self.task.send('get_build_components', data)

    def add_build_items(self, items):
        self.switch_tab_status(True)
        self.task.rsp_sgl.disconnect(self.add_build_items)
        self.comboBoxVComponent.clear()
        self.comboBoxVComponent.addItems(items)
        if 'dayu200' in items:
            self.comboBoxVComponent.setCurrentText('dayu200')

    def sync_trigger_tag_component(self, text):
        """更新tag二分的形态选择"""
        if 'ohos-sdk' in text:
            self.comboBoxTrComponent.setCurrentText('ohos-sdk')
            self.comboBoxTComponent.setCurrentText('ohos-sdk')
        else:
            self.comboBoxTrComponent.setCurrentText('dayu200')
            self.comboBoxTComponent.setCurrentText('dayu200')

    def show_version_build_table_menu(self, pos):
        menu = QMenu()
        item = self.tableWidgetVersionBuild.itemAt(pos)
        copy_action = menu.addAction('复制链接')
        open_action = menu.addAction('打开链接')
        action = menu.exec_(self.tableWidgetVersionBuild.viewport().mapToGlobal(pos))
        if action == copy_action:
            QApplication.clipboard().setText(item.text())
        elif action == open_action:
            webbrowser.open(item.text())

    def fetch_daily_build_version(self):
        if self.loop_times and self.stage == self.tabWidgetLeft.tabText(self.tabWidgetLeft.indexOf(self.tabVersion)):
            answer = QMessageBox.warning(self, self.windowTitle(), '当前二分任务未结束，重新筛选将会清空当前筛选出的版本包，确定要重新筛选吗？',
                                         QMessageBox.Yes | QMessageBox.No)
            if answer != QMessageBox.Yes:
                return

        if not self.comboBoxVComponent.currentText():
            QMessageBox.warning(self, self.windowTitle(), '请先点击左侧搜索按钮并从下拉菜单中选择一条流水线')
            return
        # 初始化任务
        self.init_task()

        request_body = {
            'projectName': self.comboBoxProject.currentText(),
            'branch': self.comboBoxBranch.currentText(),
            'startTime': self.dateEditVersionStart.date().toString('yyyyMMdd') + '000000',
            'endTime': self.dateEditVersionEnd.date().toString('yyyyMMdd') + '235959',
            'component': self.comboBoxVComponent.currentText(),
        }

        self.switch_tab_status(False)
        self.task.rsp_sgl.connect(self._create_version_table)
        self.task.send('daily_build_list', request_body)

    def _create_version_table(self, data):
        self.task.rsp_sgl.disconnect(self._create_version_table)
        self.switch_tab_status(True)
        if not data:
            return
        while self.tableWidgetVersionBuild.rowCount():
            self.tableWidgetVersionBuild.removeRow(0)
        for ver in data:
            row_count = self.tableWidgetVersionBuild.rowCount()
            self.tableWidgetVersionBuild.insertRow(row_count)

            self.tableWidgetVersionBuild.setItem(row_count, 0, QTableWidgetItem(ver.get('build_start_time')))
            self.tableWidgetVersionBuild.setItem(row_count, 1, QTableWidgetItem(ver.get('img_obs_path')))
            # 用Qt.UserRole + 1来保存该笔数据的二分结果
            self.tableWidgetVersionBuild.item(row_count, 1).setData(Qt.UserRole + 1, '')

    def init_trigger_build_tab(self):
        self.dateTimeEditTriggerStart.setDate(QDateTime.currentDateTime().date())
        self.dateTimeEditTriggerEnd.setDateTime(QDateTime.currentDateTime())
        self.comboBoxTrComponent.currentTextChanged.connect(self.comboBoxTComponent.setCurrentText)
        self.pushButtonFetchTriggerBuild.clicked.connect(self.fetch_trigger_build)
        # 表格添加头部点击取消选择事件和表格内容右键菜单
        self.tableWidgetTriggerBuild.horizontalHeader().sectionClicked.connect(self.tableWidgetTriggerBuild.clearSelection)
        self.tableWidgetTriggerBuild.setContextMenuPolicy(Qt.CustomContextMenu)
        self.tableWidgetTriggerBuild.customContextMenuRequested.connect(self.show_trigger_build_table_menu)

    def fetch_trigger_build(self):
        if self.loop_times and self.stage == self.tabWidgetLeft.tabText(self.tabWidgetLeft.indexOf(self.tabTrigger)):
            answer = QMessageBox.warning(self, self.windowTitle(), '当前二分任务未结束，重新筛选将会清空当前筛选出的pr，确定要重新筛选吗？',
                                         QMessageBox.Yes | QMessageBox.No)
            if answer != QMessageBox.Yes:
                return
        # 初始化任务
        self.init_task()

        request_params = {
            'projectName': self.comboBoxProject.currentText(),
            'branch': self.comboBoxBranch.currentText(),
            'startTime': self.dateTimeEditTriggerStart.dateTime().toString('yyyyMMddhhmmss'),
            'endTime': self.dateTimeEditTriggerEnd.dateTime().toString('yyyyMMddhhmmss'),
        }
        self.task.rsp_sgl.connect(self._create_trigger_build_table)
        self.task.send('fetch_trigger_build', request_params, self.comboBoxTrComponent.currentText())

    def _create_trigger_build_table(self, rsp_data):
        if rsp_data == '开始':
            while self.tableWidgetTriggerBuild.rowCount():
                self.tableWidgetTriggerBuild.removeRow(0)
            self.switch_tab_status(False)
            self.parent.statusbar.showMessage('正在筛选该时间段内触发的构建信息...')
        elif rsp_data == '结束':
            self.switch_tab_status(True)
            self.parent.statusbar.showMessage('筛选完成', 2000)
            self.task.rsp_sgl.disconnect(self._create_trigger_build_table)
        else:
            # owner = rsp_data.get('owner')
            timestamp = rsp_data.get('timestamp')
            pr_url = rsp_data.get('pr_url')
            img_link = rsp_data.get('img_link')
            row_count = self.tableWidgetTriggerBuild.rowCount()
            self.tableWidgetTriggerBuild.insertRow(row_count)
            # self.tableWidgetTriggerBuild.setItem(row_count, 0, QTableWidgetItem(owner))
            self.tableWidgetTriggerBuild.setItem(row_count, 0, QTableWidgetItem(timestamp))
            self.tableWidgetTriggerBuild.setItem(row_count, 1, QTableWidgetItem(pr_url))
            self.tableWidgetTriggerBuild.item(row_count, 1).setData(Qt.UserRole, img_link)
            # 用Qt.UserRole + 1来保存该笔数据的二分结果
            self.tableWidgetTriggerBuild.item(row_count, 1).setData(Qt.UserRole + 1, '')

    def show_trigger_build_table_menu(self, pos):
        menu = QMenu()
        item = self.tableWidgetTriggerBuild.itemAt(pos)
        if item is None or item.column() != 1:
            return
        copy_action = menu.addAction('复制链接')
        open_action = menu.addAction('打开链接')
        action = menu.exec_(self.tableWidgetTriggerBuild.viewport().mapToGlobal(pos))
        if action == copy_action:
            QApplication.clipboard().setText(item.text())
        elif action == open_action:
            webbrowser.open(item.text().split(',')[0].strip())

    def init_tag_build_tab(self):
        self.toolButtonFetchManifest.clicked.connect(self.fetch_manifest)
        self.dateTimeEditTagStart.setDate(QDateTime.currentDateTime().date())
        self.dateTimeEditTagEnd.setDateTime(QDateTime.currentDateTime())
        self.comboBoxTComponent.currentTextChanged.connect(self.comboBoxTrComponent.setCurrentText)
        self.pushButtonFetchTagBuild.clicked.connect(self.fetch_tag_build)
        # 表格添加头部点击取消选择事件和表格内容右键菜单
        self.tableWidgetTagBuild.horizontalHeader().sectionClicked.connect(self.tableWidgetTagBuild.clearSelection)
        self.tableWidgetTagBuild.setContextMenuPolicy(Qt.CustomContextMenu)
        self.tableWidgetTagBuild.customContextMenuRequested.connect(self.show_tag_build_table_menu)

    def fetch_manifest(self):
        self.switch_tab_status(False)
        self.task.rsp_sgl.connect(self._add_manifests)
        self.task.send('fetch_manifest', self.comboBoxProject.currentText(), self.comboBoxBranch.currentText())

    def _add_manifests(self, manifest_list):
        self.switch_tab_status(True)
        self.task.rsp_sgl.disconnect(self._add_manifests)
        self.comboBoxManifest.clear()
        self.comboBoxManifest.addItems(manifest_list)
        if 'default.xml' in manifest_list:
            self.comboBoxManifest.setCurrentText('default.xml')

    def show_tag_build_table_menu(self, pos):
        menu = QMenu()
        item = self.tableWidgetTagBuild.itemAt(pos)
        if item is None or item.column() != 2:
            return
        copy_action = menu.addAction('复制链接')
        open_action = menu.addAction('打开链接')
        action = menu.exec_(self.tableWidgetTagBuild.viewport().mapToGlobal(pos))
        if action == copy_action:
            QApplication.clipboard().setText(item.text())
        elif action == open_action:
            webbrowser.open(item.text().split(',')[0].strip())

    def fetch_tag_build(self):
        if self.loop_times and self.stage == self.tabWidgetLeft.tabText(self.tabWidgetLeft.indexOf(self.tabTag)):
            answer = QMessageBox.warning(self, self.windowTitle(), '当前二分任务未结束，重新筛选将会清空当前筛选出的pr，确定要重新筛选吗？',
                                         QMessageBox.Yes | QMessageBox.No)
            if answer != QMessageBox.Yes:
                return

        self.init_task()

        request_body = {
            "projectName": self.comboBoxProject.currentText(),
            "branch": self.comboBoxBranch.currentText(),
            "manifestFile": self.comboBoxManifest.currentText(),
            "startTime": self.dateTimeEditTagStart.date().toString('yyyy-MM-dd'),
            "endTime": self.dateTimeEditTagEnd.date().toString('yyyy-MM-dd'),
            "searchName": self.lineEditPRKeywords.text().strip()
        }

        component = self.comboBoxTComponent.currentText()
        start_time = self.dateTimeEditTagStart.dateTime().toString("yyyy-MM-dd hh:mm:ss")
        end_time = self.dateTimeEditTagEnd.dateTime().toString("yyyy-MM-dd hh:mm:ss")

        self.task.rsp_sgl.connect(self._create_tag_table)
        self.task.send('get_tag_build', request_body, component, start_time, end_time)

    def _create_tag_table(self, response_data):
        if response_data == '开始':
            while self.tableWidgetTagBuild.rowCount():
                self.tableWidgetTagBuild.removeRow(0)
            self.switch_tab_status(False)
            self.parent.statusbar.showMessage('正在筛选该时间段内合入的pr...')
        elif response_data == '结束':
            self.switch_tab_status(True)
            self.parent.statusbar.showMessage('筛选完成', 2000)
            self.task.rsp_sgl.disconnect(self._create_tag_table)
        else:
            merge_time = response_data.get('merge_time')
            version_url = response_data.get('version_url')
            pr = response_data.get('pr')
            build_time = response_data.get('build_time')
            row_count = self.tableWidgetTagBuild.rowCount()
            self.tableWidgetTagBuild.insertRow(row_count)
            self.tableWidgetTagBuild.setItem(row_count, 0, QTableWidgetItem(build_time))
            self.tableWidgetTagBuild.setItem(row_count, 1, QTableWidgetItem(merge_time))
            self.tableWidgetTagBuild.setItem(row_count, 2, QTableWidgetItem(pr))
            self.tableWidgetTagBuild.item(row_count, 2).setData(Qt.UserRole, version_url)
            # 用Qt.UserRole + 1来保存该笔数据的二分结果
            self.tableWidgetTagBuild.item(row_count, 2).setData(Qt.UserRole + 1, '')

    def start_task(self):
        if self.tabWidgetLeft.currentWidget() == self.tabVersion:
            self.version_dichotomy()
        elif self.tabWidgetLeft.currentWidget() == self.tabTrigger:
            self.trigger_build_dichotomy()
        elif self.tabWidgetLeft.currentWidget() == self.tabTag:
            self.tag_dichotomy()

    def version_dichotomy(self):
        """
        版本二分
        :return:
        """
        # 校验所选的形态是否支持
        config_data = Config.read_config()
        component = self.comboBoxVComponent.currentText()
        if 'dayu200' in component:
            if len(self.parent.devices) != 1:
                QtWidgets.QMessageBox.warning(self, self.windowTitle(), '请确保有且仅有一台RK3568设备连接')
                return
            img_save_path = config_data.get('dayu200_path')
            if not img_save_path or not os.path.exists(img_save_path):
                QMessageBox.warning(self, self.windowTitle(), '请先点击左上角“设置”按钮设置dayu200镜像保存位置')
                return
        elif 'sdk' in component:
            img_save_path = config_data['sdk_path']
            ide_config_path = config_data['ide_path']
            if not img_save_path or not os.path.exists(img_save_path) or not ide_config_path or not os.path.exists(ide_config_path):
                QMessageBox.warning(self, self.windowTitle(), '请先点击左上角“设置”按钮设置sdk镜像保存位置和ide配置路径')
                return
        else:
            QMessageBox.warning(self, self.windowTitle(), '暂不支持对该形态二分')
            return
        # 校验数据是否合法
        if not self.tableWidgetVersionBuild.rowCount():
            QMessageBox.warning(self, self.windowTitle(), '版本构建列表为空')
            return
        if not self.tableWidgetVersionBuild.selectedIndexes():
            QMessageBox.warning(self, self.windowTitle(), '请先从左侧版本构建列表中选择一个版本')
            return

        if self.confirmed:
            self.loop_times += 1
            self.stage = self.tabWidgetLeft.tabText(self.tabWidgetLeft.currentIndex())
            self.current_version_row = self.tableWidgetVersionBuild.currentRow()
            self.parent.statusbar.showMessage('{}，当前版本序号：{}，总计第 {} 轮二分...'.format(self.stage, self.current_version_row + 1, self.loop_times))
            download_link = self.tableWidgetVersionBuild.item(self.current_version_row, 1).text()

            self.task.step_sgl.connect(self.show_progress)
            self.task.console_sgl.connect(self.show_console)
            if 'dayu200' in component:
                device = self.parent.devices[0]
                self.task.send('dayu200_dichotomy', device, download_link, img_save_path, self.checkBoxUboot.isChecked())
            else:
                self.task.send('sdk_dichotomy', download_link, img_save_path, ide_config_path)
        else:
            self.ask_user_confirm()

    def trigger_build_dichotomy(self):
        """
        触发构建时间二分
        :return:
        """
        config_data = Config.read_config()
        component = self.comboBoxTrComponent.currentText()
        if 'dayu200' in component:
            if len(self.parent.devices) != 1:
                QtWidgets.QMessageBox.warning(self, self.windowTitle(), '请确保有且仅有一台RK3568设备连接')
                return
            img_save_path = config_data.get('dayu200_path')
            if not img_save_path or not os.path.exists(img_save_path):
                QMessageBox.warning(self, self.windowTitle(), '请先点击左上角“设置”按钮设置dayu200镜像保存位置')
                return
        elif 'sdk' in component:
            img_save_path = config_data['sdk_path']
            ide_config_path = config_data['ide_path']
            if not img_save_path or not os.path.exists(img_save_path) or not ide_config_path or not os.path.exists(ide_config_path):
                QMessageBox.warning(self, self.windowTitle(), '请先点击左上角“设置”按钮设置sdk镜像保存位置和ide配置路径')
                return
        else:
            QMessageBox.warning(self, self.windowTitle(), '暂不支持对该形态二分')
            return

        if not self.tableWidgetTriggerBuild.rowCount():
            QMessageBox.warning(self, self.windowTitle(), '没有pr数据，请先筛选出符合条件的pr')
            return
        if not self.tableWidgetTriggerBuild.selectedIndexes():
            QMessageBox.warning(self, self.windowTitle(), '请先从左侧列表中选择一笔pr')
            return

        if self.confirmed:
            self.loop_times += 1
            self.stage = self.tabWidgetLeft.tabText(self.tabWidgetLeft.currentIndex())
            self.current_trigger_row = self.tableWidgetTriggerBuild.currentRow()
            self.parent.statusbar.showMessage('{}，当前pr序号：{}，总计第 {} 轮二分...'.format(self.stage, self.current_trigger_row + 1, self.loop_times))
            self.task.step_sgl.connect(self.show_progress)
            self.task.console_sgl.connect(self.show_console)
            download_link = self.tableWidgetTriggerBuild.item(self.current_trigger_row, 1).data(Qt.UserRole)
            if 'dayu200' in component:
                device = self.parent.devices[0]
                self.task.send('dayu200_dichotomy', device, download_link, img_save_path, self.checkBoxUboot.isChecked())
            elif 'sdk' in component:
                self.task.send('sdk_dichotomy', download_link, img_save_path, ide_config_path)
        else:
            self.ask_user_confirm()

    def tag_dichotomy(self):
        """
        tag点二分
        :return:
        """
        config_data = Config.read_config()
        component = self.comboBoxTComponent.currentText()
        if 'dayu200' in component:
            if len(self.parent.devices) != 1:
                QtWidgets.QMessageBox.warning(self, self.windowTitle(), '请确保有且仅有一台RK3568设备连接')
                return
            img_save_path = config_data.get('dayu200_path')
            if not img_save_path or not os.path.exists(img_save_path):
                QMessageBox.warning(self, self.windowTitle(), '请先点击左上角“设置”按钮设置dayu200镜像保存位置')
                return
        elif 'sdk' in component:
            img_save_path = config_data['sdk_path']
            ide_config_path = config_data['ide_path']
            if not img_save_path or not os.path.exists(img_save_path) or not ide_config_path or not os.path.exists(ide_config_path):
                QMessageBox.warning(self, self.windowTitle(), '请先点击左上角“设置”按钮设置sdk镜像保存位置和ide配置路径')
                return
        else:
            QMessageBox.warning(self, self.windowTitle(), '暂不支持对该形态二分')
            return

        if not self.tableWidgetTagBuild.rowCount():
            QMessageBox.warning(self, self.windowTitle(), '没有pr数据，请先筛选出符合条件的pr')
            return
        if not self.tableWidgetTagBuild.selectedIndexes():
            QMessageBox.warning(self, self.windowTitle(), '请先从左侧列表中选择一笔pr')
            return
        if self.confirmed:
            self.loop_times += 1
            self.stage = self.tabWidgetLeft.tabText(self.tabWidgetLeft.currentIndex())
            self.current_tag_row = self.tableWidgetTagBuild.currentRow()
            self.parent.statusbar.showMessage('{}，当前pr序号：{}，总计第 {} 轮二分...'.format(self.stage, self.current_tag_row + 1, self.loop_times))
            self.task.step_sgl.connect(self.show_progress)
            self.task.console_sgl.connect(self.show_console)
            download_link = self.tableWidgetTagBuild.item(self.current_tag_row, 2).data(Qt.UserRole)
            if 'dayu200' in component:
                device = self.parent.devices[0]
                self.task.send('dayu200_dichotomy', device, download_link, img_save_path, self.checkBoxUboot.isChecked())
            elif 'sdk' in component:
                self.task.send('sdk_dichotomy', download_link, img_save_path, ide_config_path)
        else:
            self.ask_user_confirm()

    def show_progress(self, msg):
        current_time = QDateTime.currentDateTime().toString('hh:mm:ss')
        if msg.startswith('下载中'):
            html = self.textBrowser.toHtml()
            if not self.download_progress:
                self.download_progress = msg
                self.textBrowser.append('[{}] {}'.format(current_time, msg.replace('\n', '<br>')))
            else:
                new_html = html.replace(self.download_progress, msg)
                self.textBrowser.setHtml(new_html)
                self.download_progress = msg
        else:
            self.download_progress = ''
            if msg == '开始':
                self.start()
            elif msg == '结束':
                self.finish()
            elif msg == '异常':
                self.crashed()
            else:
                self.textBrowser.append('[{}] {}'.format(current_time, msg.replace('\n', '<br>')))

    def show_console(self, console_log):
        self.textBrowserConsole.append(console_log.replace('\n', '<br>'))

    def start(self):
        self.parent.enter_busy()
        self.switch_tab_status(False)
        self.textBrowser.clear()
        self.textBrowserConsole.clear()

    def finish(self):
        self.parent.exit_busy()
        self.switch_tab_status(True)
        self.task.step_sgl.disconnect(self.show_progress)
        self.task.console_sgl.disconnect(self.show_console)
        self.ask_user_confirm()

    def crashed(self):
        self.parent.exit_busy()
        self.switch_tab_status(True)
        self.task.step_sgl.disconnect(self.show_progress)
        self.task.console_sgl.disconnect(self.show_console)

        if self.stage == self.tabWidgetLeft.tabText(self.tabWidgetLeft.indexOf(self.tabVersion)):
            self.tableWidgetVersionBuild.clearSelection()
            self.mark_result(self.tableWidgetVersionBuild, self.current_version_row, '异常')
        elif self.stage == self.tabWidgetLeft.tabText(self.tabWidgetLeft.indexOf(self.tabTrigger)):
            self.tableWidgetTriggerBuild.clearSelection()
            self.mark_result(self.tableWidgetTriggerBuild, self.current_trigger_row, '异常')
        else:
            self.tableWidgetTagBuild.clearSelection()
            self.mark_result(self.tableWidgetTagBuild, self.current_tag_row, '异常')

    def create_msg_box(self, text, *buttons):
        msg_box = QMessageBox(self)
        msg_box.setWindowTitle(self.windowTitle())
        msg_box.setWindowIcon(self.windowIcon())
        msg_box.setText(text)
        for button in buttons:
            msg_box.addButton(button, msg_box.ActionRole)
        return msg_box

    def ask_user_confirm(self):
        confirm_box = self.create_msg_box('请确认问题是否复现？', '是，已复现', '否，未复现', '稍后确认')
        # ans = ''
        while True:
            confirm_box.exec_()
            if confirm_box.clickedButton().text() == '是，已复现':
                double_confirm_box = self.create_msg_box('我确认问题【已复现】！', '是', '重新确认')
                double_confirm_box.exec_()
                if double_confirm_box.clickedButton().text() == '是':
                    double_confirm_box.close()
                    ans = confirm_box.clickedButton().text()
                    break
                else:
                    double_confirm_box.close()
            elif confirm_box.clickedButton().text() == '否，未复现':
                double_confirm_box = self.create_msg_box('我确认问题【没复现】！', '是', '重新确认')
                double_confirm_box.exec_()
                if double_confirm_box.clickedButton().text() == '是':
                    double_confirm_box.close()
                    ans = confirm_box.clickedButton().text()
                    break
                else:
                    double_confirm_box.close()
            elif confirm_box.clickedButton().text() == '稍后确认':
                ans = confirm_box.clickedButton().text()
                break
        if ans == '是，已复现':
            confirm_box.close()
            self.confirmed = True
            self.bug_occurred()
        elif ans == '否，未复现':
            confirm_box.close()
            self.confirmed = True
            self.bug_not_occurred()
        else:
            confirm_box.close()
            self.confirmed = False

    def bug_occurred(self):
        if self.stage == self.tabWidgetLeft.tabText(self.tabWidgetLeft.indexOf(self.tabVersion)):
            self.mark_result(self.tableWidgetVersionBuild, self.current_version_row, '复现')
        elif self.stage == self.tabWidgetLeft.tabText(self.tabWidgetLeft.indexOf(self.tabTrigger)):
            self.mark_result(self.tableWidgetTriggerBuild, self.current_trigger_row, '复现')
        else:
            # 将当前的行标记为红色
            self.mark_result(self.tableWidgetTagBuild, self.current_tag_row, '复现')
            for row in range(self.current_tag_row + 1, self.tableWidgetTagBuild.rowCount()):
                # 如果该笔pr的合入时间比当前这笔问题复现的pr的镜像构建时间晚，说明不是这笔pr引入的，将当前的行标记为灰色，排除了这些pr的可能性
                if self.tableWidgetTagBuild.item(row, 1).text() > self.tableWidgetTagBuild.item(self.current_tag_row, 0).text():
                    self.mark_result(self.tableWidgetTagBuild, row, '排除')
                else:
                    break

    def bug_not_occurred(self):
        if self.stage == self.tabWidgetLeft.tabText(self.tabWidgetLeft.indexOf(self.tabVersion)):
            self.mark_result(self.tableWidgetVersionBuild, self.current_version_row, '未复现')
        elif self.stage == self.tabWidgetLeft.tabText(self.tabWidgetLeft.indexOf(self.tabTrigger)):
            self.mark_result(self.tableWidgetTriggerBuild, self.current_trigger_row, '未复现')
        else:
            self.mark_result(self.tableWidgetTagBuild, self.current_tag_row, '未复现')
            for row in range(self.tableWidgetTagBuild.rowCount() - 1, self.current_tag_row, -1):
                if self.tableWidgetTagBuild.item(row, 1).text() < self.tableWidgetTagBuild.item(self.current_tag_row, 0).text():
                    self.mark_result(self.tableWidgetTagBuild, row, '未复现')
                else:
                    break

    def init_task(self):
        self.stage = None
        self.loop_times = 0
        self.download_progress = ''

        self.current_version_row = 0
        self.current_trigger_row = 0
        self.current_tag_row = 0

        self.confirmed = True

        self.parent.statusbar.clearMessage()

    def mark_result(self, table, row, result):
        color = self.color_dict.get(result)
        if not table or not color:
            return
        for col in range(table.columnCount()):
            table.item(row, col).setBackgroundColor(QColor(color))
            if col == table.columnCount() - 1:
                table.item(row, col).setData(Qt.UserRole + 1, result)

    def export_dichotomy_result(self):
        if not self.tableWidgetVersionBuild.rowCount() and not self.tableWidgetTriggerBuild.rowCount() and not self.tableWidgetTagBuild.rowCount():
            QMessageBox.warning(self, self.windowTitle(), '页面数据为空')
            return
        try:
            current_time = QDateTime().currentDateTime().toString('yyyyMMdd_hhmmss')
            default_name = current_time + '_dichotomy.xlsx'
            filename, _ = QFileDialog.getSaveFileName(self, '保存', default_name, 'Excel Files(*.xlsx)')
            if not filename:
                return

            border = Border(
                left=Side(border_style='thin', color='000000'),
                right=Side(border_style='thin', color='000000'),
                top=Side(border_style='thin', color='000000'),
                bottom=Side(border_style='thin', color='000000'),
            )

            workbook = openpyxl.Workbook()

            worksheet1 = workbook.active
            worksheet1.title = '版本二分'
            self.export_version_table(worksheet1, border)

            worksheet2 = workbook.create_sheet(title='调度时间二分')
            self.export_trigger_table(worksheet2, border)

            worksheet3 = workbook.create_sheet(title='Tag点二分')
            self.export_tag_table(worksheet3, border)

            workbook.save(filename)
            QMessageBox.information(self, self.windowTitle(), '二分结果导出成功！')
        except Exception as e:
            QMessageBox.warning(self, self.windowTitle(), e.args[0])

    def export_version_table(self, worksheet, border):
        worksheet.column_dimensions['A'].width = 25
        worksheet.column_dimensions['B'].width = 100
        worksheet['A1'] = '启动时间'
        worksheet['B1'] = '镜像包下载链接'
        worksheet['C1'] = '结果'
        for row in range(self.tableWidgetVersionBuild.rowCount()):
            start_time = self.tableWidgetVersionBuild.item(row, 0).text()
            img_link = self.tableWidgetVersionBuild.item(row, 1).text()
            result = self.tableWidgetVersionBuild.item(row, 1).data(Qt.UserRole + 1)
            background_color = self.color_dict.get(result, '#FFFFFF').strip('#')
            pfill = PatternFill(fill_type='solid', start_color=background_color)

            worksheet.cell(row=row + 2, column=1, value=start_time)
            worksheet.cell(row=row + 2, column=1).fill = pfill
            worksheet.cell(row=row + 2, column=1).border = border

            worksheet.cell(row=row + 2, column=2, value=img_link)
            worksheet.cell(row=row + 2, column=2).fill = pfill
            worksheet.cell(row=row + 2, column=2).border = border

            worksheet.cell(row=row + 2, column=3, value=result)
            worksheet.cell(row=row + 2, column=3).fill = pfill
            worksheet.cell(row=row + 2, column=3).border = border

    def export_trigger_table(self, worksheet, border):
        worksheet.column_dimensions['A'].width = 25
        worksheet.column_dimensions['B'].width = 150
        worksheet.column_dimensions['C'].width = 150
        worksheet['A1'] = '调度时间'
        worksheet['B1'] = 'pr链接'
        worksheet['C1'] = '镜像包下载链接'
        worksheet['D1'] = '结果'
        for row in range(self.tableWidgetTriggerBuild.rowCount()):
            start_time = self.tableWidgetTriggerBuild.item(row, 0).text()
            pr = self.tableWidgetTriggerBuild.item(row, 1).text()
            img_link = self.tableWidgetTriggerBuild.item(row, 1).data(Qt.UserRole)
            result = self.tableWidgetTriggerBuild.item(row, 1).data(Qt.UserRole + 1)

            background_color = self.color_dict.get(result, '#FFFFFF').strip('#')
            pfill = PatternFill(fill_type='solid', start_color=background_color)

            worksheet.cell(row=row + 2, column=1, value=start_time)
            worksheet.cell(row=row + 2, column=1).fill = pfill
            worksheet.cell(row=row + 2, column=1).border = border

            worksheet.cell(row=row + 2, column=2, value=pr)
            worksheet.cell(row=row + 2, column=2).fill = pfill
            worksheet.cell(row=row + 2, column=2).border = border

            worksheet.cell(row=row + 2, column=3, value=img_link)
            worksheet.cell(row=row + 2, column=3).fill = pfill
            worksheet.cell(row=row + 2, column=3).border = border

            worksheet.cell(row=row + 2, column=4, value=result)
            worksheet.cell(row=row + 2, column=4).fill = pfill
            worksheet.cell(row=row + 2, column=4).border = border

    def export_tag_table(self, worksheet, border):
        worksheet.column_dimensions['A'].width = 25
        worksheet.column_dimensions['B'].width = 25
        worksheet.column_dimensions['C'].width = 100
        worksheet['A1'] = '镜像构建时间'
        worksheet['B1'] = 'Tag发布时间'
        worksheet['C1'] = '合入pr'
        worksheet['D1'] = '镜像包下载链接'
        worksheet['E1'] = '结果'
        for row in range(self.tableWidgetTagBuild.rowCount()):
            build_time = self.tableWidgetTagBuild.item(row, 0).text()
            merge_time = self.tableWidgetTagBuild.item(row, 1).text()
            pr = self.tableWidgetTagBuild.item(row, 2).text()
            img_link = self.tableWidgetTagBuild.item(row, 2).data(Qt.UserRole)
            result = self.tableWidgetTagBuild.item(row, 2).data(Qt.UserRole + 1)

            background_color = self.color_dict.get(result, '#FFFFFF').strip('#')
            pfill = PatternFill(fill_type='solid', start_color=background_color)

            worksheet.cell(row=row + 2, column=1, value=build_time)
            worksheet.cell(row=row + 2, column=1).fill = pfill
            worksheet.cell(row=row + 2, column=1).border = border

            worksheet.cell(row=row + 2, column=2, value=merge_time)
            worksheet.cell(row=row + 2, column=2).fill = pfill
            worksheet.cell(row=row + 2, column=2).border = border

            worksheet.cell(row=row + 2, column=3, value=pr)
            worksheet.cell(row=row + 2, column=3).fill = pfill
            worksheet.cell(row=row + 2, column=3).border = border

            worksheet.cell(row=row + 2, column=4, value=img_link)
            worksheet.cell(row=row + 2, column=4).fill = pfill
            worksheet.cell(row=row + 2, column=4).border = border

            worksheet.cell(row=row + 2, column=5, value=result)
            worksheet.cell(row=row + 2, column=5).fill = pfill
            worksheet.cell(row=row + 2, column=5).border = border

    def import_dichotomy_result(self):
        if self.loop_times and self.stage == self.tabWidgetLeft.tabText(self.tabWidgetLeft.indexOf(self.tabTag)):
            answer = QMessageBox.warning(self, self.windowTitle(), '当前二分任务未结束，重新导入将会重置任务，确定要导入吗？',
                                         QMessageBox.Yes | QMessageBox.No)
            if answer != QMessageBox.Yes:
                return

        self.init_task()
        try:
            file_path, _ = QFileDialog.getOpenFileName(self, '选择', '.', 'Excel Files(*.xlsx)')
            if not file_path:
                return
            version_df = pd.read_excel(file_path, sheet_name='版本二分').to_dict('records')
            self.import_version_table(version_df)
            trigger_df = pd.read_excel(file_path, sheet_name='调度时间二分').to_dict('records')
            self.import_trigger_table(trigger_df)
            tag_df = pd.read_excel(file_path, sheet_name='Tag点二分').to_dict('records')
            self.import_tag_table(tag_df)
        except:
            pass

    def import_version_table(self, df):
        # 清空版本构建表
        while self.tableWidgetVersionBuild.rowCount():
            self.tableWidgetVersionBuild.removeRow(0)
        for ver in df:
            row_count = self.tableWidgetVersionBuild.rowCount()
            self.tableWidgetVersionBuild.insertRow(row_count)

            build_time = ver.get('启动时间') or ''
            img_link = ver.get('镜像包下载链接') or ''
            result = ver.get('结果') or ''
            self.tableWidgetVersionBuild.setItem(row_count, 0, QTableWidgetItem(build_time))
            self.tableWidgetVersionBuild.setItem(row_count, 1, QTableWidgetItem(img_link))
            # 用Qt.UserRole + 1来保存该笔数据的二分结果
            self.tableWidgetVersionBuild.item(row_count, 1).setData(Qt.UserRole + 1, result)
            self.mark_result(self.tableWidgetVersionBuild, row_count, result)
            if row_count == 0:
                if re.search('dayu200.*\.tar\.gz', img_link):
                    self.comboBoxVComponent.addItem('dayu200')
                elif re.search('ohos-sdk.*\.tar\.gz', img_link):
                    self.comboBoxVComponent.addItem('ohos-sdk')

    def import_trigger_table(self, df):
        while self.tableWidgetTriggerBuild.rowCount():
            self.tableWidgetTriggerBuild.removeRow(0)
        for trigger in df:
            row_count = self.tableWidgetTriggerBuild.rowCount()
            self.tableWidgetTriggerBuild.insertRow(row_count)

            result = trigger.get('结果') or ''
            self.tableWidgetTriggerBuild.setItem(row_count, 0, QTableWidgetItem(trigger.get('调度时间')))
            self.tableWidgetTriggerBuild.setItem(row_count, 1, QTableWidgetItem(trigger.get('pr链接')))
            self.tableWidgetTriggerBuild.item(row_count, 1).setData(Qt.UserRole, trigger.get('镜像包下载链接'))
            self.tableWidgetTriggerBuild.item(row_count, 1).setData(Qt.UserRole + 1, result)
            self.mark_result(self.tableWidgetTriggerBuild, row_count, result)

    def import_tag_table(self, df):
        while self.tableWidgetTagBuild.rowCount():
            self.tableWidgetTagBuild.removeRow(0)
        for tag in df:
            row_count = self.tableWidgetTagBuild.rowCount()
            self.tableWidgetTagBuild.insertRow(row_count)

            result = tag.get('结果') or ''
            self.tableWidgetTagBuild.setItem(row_count, 0, QTableWidgetItem(tag.get('镜像构建时间')))
            self.tableWidgetTagBuild.setItem(row_count, 1, QTableWidgetItem(tag.get('Tag发布时间')))
            self.tableWidgetTagBuild.setItem(row_count, 2, QTableWidgetItem(tag.get('合入pr')))
            self.tableWidgetTagBuild.item(row_count, 2).setData(Qt.UserRole, tag.get('镜像包下载链接'))
            self.tableWidgetTagBuild.item(row_count, 2).setData(Qt.UserRole + 1, result)
            self.mark_result(self.tableWidgetTagBuild, row_count, result)
